/*
@STI LICENSE
(C) 2006-2008 Universit�t Innsbruck.

This file is part of myOntology.

myOntologyis free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

myOntologyis distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with myOntology.  If not, see <http://www.gnu.org/licenses/>.

@STI LICENSE */
/**
 * 
 */
package org.myontology.controller;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.log4j.Logger;

/**
 * @author <a href="michael.hackl@sti2.at">Michael Hackl</a>
 * Project: Myontology - www.myontology.org
 * STI
 *
 *  The dependency injection (DI) controller is used to inject helper classes 
 *  like Loggers and RepositoryConnections into actions or other classes where they are needed
 *  
 *  advantages are: unit testing can be done easier via mock helpers
 *  				different configurations for production/testing/development can be handled at one place
 *  
 *  the implementations of the dependency- interfaces/baseclasses are declared via the implement methods
 *  
 *  to use the dependency injection, just declare a member variable of the interface or base class of your helper class
 *  after creating an instance of the class, pass it as a parameter to the injectDepencies method.
 *  
 *  for every member field of this instance the DIController searches for an implementation
 *  if one is found, this implementation is passed to the member field
 *  in the normal case this introduces a singelton pattern, therefore just one instance of the helper object is injected  
 *  into all demanding objects
 *  
 *  to have more control over this process you can also pass a DependencyProvider as implementation.  
 *  the DependencyProvider can be used as a factory, pool,...
 *  
 *  look for examples in: org.myontology.controller.test.ControllerTest (project: tests)
 */
public class DIController {

	protected Logger logger = Logger.getLogger(getClass());
	private Map<Class, Object> dependencies= new ConcurrentHashMap<Class, Object>();
	private Map<Class,Set<Field>> fieldCache= new ConcurrentHashMap<Class, Set<Field>>();
	private Map<Class,Map<Class,Object>> objectSpecificDependencies = new ConcurrentHashMap<Class, Map<Class,Object>>();
	public static final ValueHandler<List<LifeCycleController>> DEPENDENCY_HANDLERS = new ValueHandler<List<LifeCycleController>>(AttributeLevel.REQUEST,DIController.class);
	public static final ValueHandler<List> OBJECTS_WITH_INJECTION = new ValueHandler<List>(AttributeLevel.REQUEST,DIController.class);
	
	/**
	 * implement couples one interface/baseclass with an implementation for all dependency injected classes
	 * can be overridden for a single class
	 * @param interfa, the type of the interface/baseclass that has to be declared as member field
	 * @param impl, the singleton helper class or a DependencyProvider
	 */
	public void implement(Class interfa,Object impl) {
		dependencies.put(interfa, impl);
	}
	
	
	/**
	 * implement couples one interface/baseclass with an implementation for one specific dependency injected class
	 * @param forObject, declares the specific class, where a special dependency injection should be used
	 * @param interfa, the type of the interface/baseclass that has to be declared as member field
	 * @param impl, the singleton helper class or a DependencyProvider
	 */
	public void implement(Class forObject,Class interfa,Object impl) {
		Map<Class,Object> osMap = objectSpecificDependencies.get(forObject);
		if (osMap==null) {
			osMap=new ConcurrentHashMap<Class, Object>();
			objectSpecificDependencies.put(forObject, osMap);
		}
		osMap.put(interfa,impl);
	}
	
	protected void prepareDependencyCache(Object action,AttributeHolder attributes) {
		Set<Field> fieldSet = new CopyOnWriteArraySet<Field>();
		Field[] fields = action.getClass().getDeclaredFields();
		searchDependencyFields(action, fieldSet, fields,action.getClass());
		fieldCache.put(action.getClass(), fieldSet);
		injectDepencies(action,attributes);
	}

	private void searchDependencyFields(Object action, Set<Field> fieldSet,
			Field[] fields,Class clazz) {
		for (int i=0;i<fields.length;i++) {
			Class fieldClass = fields[i].getType();
			Map<Class, Object> osMap = objectSpecificDependencies.get(action.getClass());
			if ((osMap!=null && osMap.containsKey(fieldClass)) || dependencies.containsKey(fieldClass))
				fieldSet.add(fields[i]);
		}
		Class parent = clazz.getSuperclass();
		if (parent!=null){
			searchDependencyFields(action, fieldSet, parent.getDeclaredFields(), parent);
		}
	}
	
	public void injectDepencies(Object action,AttributeHolder attributes) {
		List l = OBJECTS_WITH_INJECTION.getValue(attributes);
		if (l==null) {
			l=new ArrayList();
			OBJECTS_WITH_INJECTION.setValue(attributes, l);
		}
		l.add(action);
		injectDepencies(action, attributes,new StandardResolver());
	}
	
	public void removeDepencies(AttributeHolder attributes) {
		List l = OBJECTS_WITH_INJECTION.getValue(attributes);
		if (l!=null) {
			for (Iterator it = l.iterator();it.hasNext();) {
				injectDepencies(it.next(), attributes,new NullResolver());
			}
		}
	}
	
	/**
	 * is used to inject the dependencies into one specific instance
	 * @param action, the instance that gets the injections
	 */
	public void injectDepencies(Object action,AttributeHolder attributes,DependencyResolver resolver) {
		Set<Field> fieldSet = fieldCache.get(action.getClass());
		if (fieldSet==null) {
			prepareDependencyCache(action,attributes);
			return;
		}
		for (Iterator<Field> it = fieldSet.iterator();it.hasNext();) {
			Field f = it.next();
			Object dependency=null;
			f.setAccessible(true);
			try {
				if (f.get(action)!=null || Modifier.isFinal(f.getModifiers()) || Modifier.isStatic(f.getModifiers()))
					continue;
				dependency = resolver.findDependency(action, attributes, f, dependency);
			
			
				f.set(action, dependency);
			} catch (Exception e) {
				logger.error("initAction: "+action.getClass().getName(),e);
			} 
		}
		for (Iterator<Field> it = fieldSet.iterator();it.hasNext();) {
			Field f = it.next();
			try {
				if (Modifier.isFinal(f.getModifiers()) || Modifier.isStatic(f.getModifiers()))
					continue;
				Object val = f.get(action);
				if (val!=null) {
					injectDepencies(val, attributes,resolver);
				}
			} catch (Exception e) {
				logger.error("error in retrieving value from field: "+f.getName()+" in object: "+action.getClass().getName());
			} 
		}
	}


	
	
	public void cleanDependencies(AttributeHolder attributes) {
		List<LifeCycleController> list = DEPENDENCY_HANDLERS.getValue(attributes);
		if (list!=null) {
			for (Iterator<LifeCycleController> it = list.iterator();it.hasNext();) {
				it.next().clean(attributes);
			}
		}
		removeDepencies(attributes);
	}
	
	public void finishDependencies(AttributeHolder attributes) {
		List<LifeCycleController> list = DEPENDENCY_HANDLERS.getValue(attributes);
		if (list!=null) {
			for (Iterator<LifeCycleController> it = list.iterator();it.hasNext();) {
				it.next().finish(attributes);
			}
		}
		removeDepencies(attributes);
	}
	
	interface DependencyResolver {
		public Object findDependency(Object action, AttributeHolder attributes,
				Field f, Object dependency);
	}
	
	class NullResolver implements DependencyResolver {

		public Object findDependency(Object action, AttributeHolder attributes,
				Field f, Object dependency) {
			return null;
		}
		
	}
	
	class StandardResolver implements DependencyResolver{
		/**
		 * @param action
		 * @param attributes
		 * @param f
		 * @param dependency
		 * @return
		 */
		public Object findDependency(Object action, AttributeHolder attributes,
				Field f, Object dependency) {
			Map<Class, Object> osMap = objectSpecificDependencies.get(action.getClass());
			if (osMap!=null) {
				dependency=osMap.get(f.getType());
			}
			if (dependency==null) {
				dependency = dependencies.get(f.getType());
			}
			if (dependency==null) {
				logger.error("no dependency object found for: "+action.getClass().getName()+"#"+f.getName()+" "+f.getType());
			}
			if (dependency instanceof LifeCycleController) {
				LifeCycleController dh = ((LifeCycleController)dependency);
				dh.init(attributes);
				List<LifeCycleController> list = DEPENDENCY_HANDLERS.getValue(attributes);
				if (list==null) {
					list = new ArrayList<LifeCycleController>();
					DEPENDENCY_HANDLERS.setValue(attributes, list);
					
				}
				list.add(dh);
				dependency = ((LifeCycleController)dependency).get(action,attributes);
			}
			if (dependency instanceof DependencyProvider) {
				dependency = ((DependencyProvider)dependency).get(action);
			}
			return dependency;
		}
	}
	
}
